﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Diagnostics.Contracts;
    using Mercury.ParticleEngine.EmitterShapes;

    /// <summary>
    /// Defines the base class for a particle emitter.
    /// </summary>
    public class ParticleEmitter
    {
        private readonly MetaParticle[] _buffer;
        private readonly Int32 _size;
        private readonly Single _term;
        private readonly IEmitterShape _shape;
        private readonly IStateManager _state;
        private Int32 _head;
        private Int32 _next;
        private Int32 _count;

        /// <summary>
        /// Gets or sets the number of particles which will be released on each trigger.
        /// </summary>
        public Int32 ReleaseQuantity { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Mercury.ParticleEngine.ParticleEmitter"/>
        /// class.
        /// </summary>
        /// <param name="capacity">The maximum number of particles which may be managed by the
        /// emitter at any given time.</param>
        /// <param name="term">The period of time in whole and fractional seconds that particles
        /// will remain active for.</param>
        /// <param name="shape">The emitter shape.</param>
        /// <param name="stateManager">The object which manages the state of the particle
        /// simulation.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// Thrown if either of the values passed to the <paramref name="shape"/> or <paramref name="stateManager"/>
        /// parameters are <c>null</c>.
        /// </exception>
        protected ParticleEmitter(Int32 capacity, TimeSpan term, IEmitterShape shape, IStateManager stateManager)
        {
            Contract.Requires<ArgumentNullException>(shape != null);
            Contract.Requires<ArgumentNullException>(stateManager != null);

            _size = capacity;
            _term = (Single)term.TotalSeconds;

            _buffer = new MetaParticle[_size];

            _shape = shape;
            _state = stateManager;

            unsafe
            {
                fixed (MetaParticle* particles = _buffer)
                {
                    for (var index = 0; index < _size; index++)
                    {
                        (particles + index)->Index = index;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the total number of currently active particles in the emitter.
        /// </summary>
        public Int32 ActiveParticlesCount
        {
            get { return _count; }
        }

        /// <summary>
        /// Updates the particle emitter, amending the age of each active particle and removing any
        /// particles which have expired.
        /// </summary>
        /// <param name="context">A pointer to an update context structure.</param>
        internal unsafe void Update(ref UpdateContext context)
        {
            if (_count == 0)
                return;

            fixed (MetaParticle* buffer = _buffer)
            {
                var expired = 0;
                var iteration = 0;

                do
                {
                    var meta = buffer + ((_head + iteration) % _size);

                    meta->Age = (context.TotalSeconds - meta->ReleaseContext.TotalSeconds) / _term;

                    if (meta->Age > 1f)
                        expired++;
                }
                while (++iteration < _count);

                if (expired > 0)
                    Pop(expired);

                var iterator = new MetaParticleIterator(buffer, _size, _head, _count);

                _state.UpdateState(ref iterator);
            }
        }

        /// <summary>
        /// Triggers the emitter, prompting the release of a new particle.
        /// </summary>
        /// <param name="context">A pointer to a trigger context structure.</param>
        internal unsafe void Trigger(ref TriggerContext context)
        {
            fixed (MetaParticle* buffer = _buffer)
            {
                var startIndex = _next;
                var released = 0;

                for (var i = 0; i < ReleaseQuantity; i++)
                {
                    var index = Push();

                    if (index < 0)
                        break;

                    var shapeResult = _shape.GetOffsetAndDirection();

                    var releaseContext = new ReleaseContext(ref context, context.Position + shapeResult.Offset, shapeResult.Direction);

                    (buffer + index)->ReleaseContext = releaseContext;

                    released++;
                }

                if (released > 0)
                {
                    var iterator = new MetaParticleIterator(buffer, _size, startIndex, released);

                    _state.InitializeState(ref iterator);
                }
            }
        }

        public unsafe void Render()
        {
            if (_count > 0)
            {
                fixed (MetaParticle* buffer = _buffer)
                {
                    var iterator = new MetaParticleIterator(buffer, _size, _head, _count);

                    _state.Render(ref iterator);
                }
            }
        }

        private Int32 Push()
        {
            if (_count == _size)
                return -1;

            var index = _next;

            _next = (_next + 1) % _size;

            _count++;

            return index;
        }

        private void Pop(Int32 count)
        {
            _head = (_head + count) % _size;

            _count -= count;
        }
    }
}